package org.jeecg.modules.iot.bind.service.impl;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.aliyuncs.exceptions.ClientException;
import com.aliyuncs.iot.model.v20180120.PubRequest;
import com.aliyuncs.iot.model.v20180120.PubResponse;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.entity.ContentType;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.exception.ChechDataAuthException;
import org.jeecg.common.exception.JeecgBoot401Exception;
import org.jeecg.common.system.util.JwtUtil;
import org.jeecg.common.util.CommonUtils;
import org.jeecg.common.util.oConvertUtils;
import org.jeecg.modules.app.util.NumberArithmeticUtils;
import org.jeecg.modules.app.v1.util.Const;
import org.jeecg.modules.iot.aio.Aio;
import org.jeecg.modules.iot.aio.AioConfiguration;
import org.jeecg.modules.iot.aio.ChannelContext;
import org.jeecg.modules.iot.aio.message.CheckMessage;
import org.jeecg.modules.iot.bind.entity.YlBindToken;
import org.jeecg.modules.iot.bind.entity.YlBindUser;
import org.jeecg.modules.iot.bind.mapper.YlBindUserMapper;
import org.jeecg.modules.iot.common.IotConst;
import org.jeecg.modules.iot.message.*;
import org.jeecg.modules.iot.service.IotDeviceService;
import org.jeecg.modules.iot.util.IotClientUtil;
import org.jeecg.modules.iot.util.QRCodeGenerator;
import org.jeecg.modules.power.device.entity.YlDevice;
import org.jeecg.modules.power.device.entity.YlDeviceCategory;
import org.jeecg.modules.power.device.service.IYlDeviceCategoryService;
import org.jeecg.modules.power.device.service.IYlDeviceService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.stereotype.Service;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.transaction.annotation.Transactional;

import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletRequest;
import java.awt.image.BufferedImage;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

import org.jeecg.modules.iot.bind.service.*;
import org.springframework.util.FileCopyUtils;
import org.springframework.web.multipart.MultipartFile;

import static org.apache.commons.codec.binary.Base64.encodeBase64String;

/**
 * @Description: 设备绑定操作员表
 * @Author: jeecg-boot
 * @Date:   2024-09-03
 * @Version: V1.0
 */
@Slf4j
@Service
public class YlBindUserServiceImpl extends ServiceImpl<YlBindUserMapper, YlBindUser> implements IYlBindUserService {

    @Autowired
    private IYlDeviceService deviceService;
    @Autowired
    private IotClientUtil iotClientUtil;
    @Value(value = "${jeecg.path.upload}")
    private String uploadpath;
    @Value(value = "${server.servlet.context-path}")
    private String path;
    @Value(value = "${server.port}")
    private String port;
    //返回图片路径
    @Value(value = "${server.compression.image-url}")
    private String imageUrl;
    @Autowired
    IotDeviceService iotDeviceService;
    @Autowired
    private IYlDeviceCategoryService deviceCategoryService;

    /**
     * 根据二维码的ID查询对应的设备信息，更新sn号
     * @param qrCodeId
     * @param rentBoxSn
     */
    public void bindDevice(String qrCodeId,String rentBoxSn){
        QueryWrapper<YlDevice> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("device_sn",qrCodeId).last("limit 1");
        YlDevice devices = deviceService.getOne(queryWrapper);
        if(oConvertUtils.isNotEmpty(devices)){
            if(!Objects.equals(devices.getDeviceQrcodeId(),qrCodeId))throw new ChechDataAuthException(rentBoxSn+"已绑定ID："+devices.getDeviceQrcodeId());
            else throw new ChechDataAuthException("已绑定,无需重复提交");
        }
        QueryWrapper<YlDevice> deviceQueryWrapper = new QueryWrapper<>();
        deviceQueryWrapper.eq("device_qrcode_id",qrCodeId).last("limit 1");
        YlDevice device = deviceService.getOne(deviceQueryWrapper);
        if(oConvertUtils.isEmpty(device))throw new ChechDataAuthException("设备信息错误，请重试");
        if(oConvertUtils.isNotEmpty(device.getDeviceSn())) {
            if (!Objects.equals(device.getDeviceSn(), rentBoxSn))
                throw new ChechDataAuthException("ID:" + qrCodeId + "已绑定SN：" + devices.getDeviceSn());
            if (Objects.equals(device.getDeviceSn(), rentBoxSn))
                throw new ChechDataAuthException("已绑定,无需重复提交");
        }
        device.setDeviceSn(rentBoxSn);
        device.setDeviceStatus(Const.deviceStatus_4);
        deviceService.updateById(device);
        /**
         * 直接从阿里云注册设备
         */
        try {
            iotDeviceService.registerDevice(rentBoxSn);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

    }

    /**
     * 根据二维码的ID查询对应的设备信息，更新sn号
     * @param qrCodeId
     * @param rentBoxSn
     */
    public void unbindDevice(String qrCodeId,String rentBoxSn){
        QueryWrapper<YlDevice> deviceQueryWrapper = new QueryWrapper<>();
        deviceQueryWrapper.eq("device_qrcode_id",qrCodeId).last("limit 1");
        YlDevice device = deviceService.getOne(deviceQueryWrapper);
        if(oConvertUtils.isEmpty(device))throw new ChechDataAuthException("设备信息错误，请重试");
        if(!Objects.equals(device.getDeviceSn(),rentBoxSn))throw new ChechDataAuthException("解绑失败，当前设备绑定的SN与扫描的SN不匹配");
        device.setDeviceSn("");
        /**解绑后设置为可删除状态*/
        device.setDeviceStatus(Const.deviceStatus_9);
        deviceService.updateById(device);
        /**
         * 从阿里云删除设备
         */
        try {
            iotDeviceService.delDevice(rentBoxSn);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    @Transactional
    public Result<?> accountLogin(YlBindUser sysUser) {
        Map<String, Object> map = new HashMap<String, Object>();
        String token = 	JwtUtil.sign(sysUser.getId(),sysUser.getUserName());
        baseMapper.delGasTokenByUserId(sysUser.getId());//清除其他token  根据用户id
        LocalDateTime expireTime = LocalDateTime.now().plusDays(7);
        String id = NumberArithmeticUtils.getTimestamp();
        baseMapper.addToken(id,sysUser.getId(),token,expireTime);
        map.put("token",token);
        return Result.OK(map);
    }

    /**
     * 校验用户token
     * @param request
     * @return
     */
    public YlBindUser getUserByToken(HttpServletRequest request){
        String token = request.getHeader("token"); // 从 http 请求头中取出 token
        YlBindToken tokens = baseMapper.getUserIdByToken(token);
        if(oConvertUtils.isEmpty(tokens))throw new JeecgBoot401Exception("无效的token，请重新登录");
        if(tokens.getExpireTime().isBefore(LocalDateTime.now())){
            baseMapper.delGasTokenByUserId(tokens.getUserId());//清除其他token  根据用户id
            throw new JeecgBoot401Exception("token已过期，请重新登录");
        }
        YlBindUser user = this.getById(tokens.getUserId());
        if(oConvertUtils.isEmpty(tokens))throw new JeecgBoot401Exception("无效的token，请重新登录");
        return user;
    }

    /**
     * 发送查询命令查询设备情况
     * @param qrcodeId
     * @return
     */
    public JSONObject queryDevice(String qrcodeId){
        QueryWrapper<YlDevice> deviceQueryWrapper = new QueryWrapper<>();
        deviceQueryWrapper.eq("device_qrcode_id",qrcodeId).last("limit 1");
        YlDevice device = deviceService.getOne(deviceQueryWrapper);
        if(oConvertUtils.isEmpty(device))return null;
        AioConfiguration configuration = Aio.getAioConfiguration(iotClientUtil.getProductKey());
        ChannelContext channelContext = configuration.queueList.getChannelContext(device.getDeviceSn(),configuration);
        CheckMessage message = new CheckMessage();
        message.setCmd(IotConst.SEND_CHECK_ALL);
        message.setMessage(IotConst.SEND_CHECK_ALL);
        message.setMac(device.getDeviceSn());
        message.setProductKey(iotClientUtil.getProductKey());
        /**
         * 发送查询指令
         */
        channelContext.checkMessageRunnable.addMsg(message);
        channelContext.checkMessageRunnable.execute();
        ReceiveUpload receiveUpload = null;
        /**
         * 查询消息队列，设备是否已经上报数据
         */
        Boolean res = true;
        int count = 1;
        while (res) {
            if(count>10){
                log.info("查询10次未查询到消息");
                break;
            }
            count++;
            try {
                Thread.sleep(1500);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            receiveUpload = channelContext.getReceiveUpload();
            if(oConvertUtils.isEmpty(receiveUpload)){
                configuration.queryMessageRunnable.addMsg(message);
                configuration.queryMessageRunnable.execute();
            }else {
                res = false;
            }
        }
        return jsonEntity(receiveUpload,device.getDeviceSn());
    }

    /**
     * sn号弹出
     * @param sn
     * @return
     */
    public ReceivePopupSN popupSn(String deviceSn,String sn){

        CheckMessage message = new CheckMessage();
        String msg = String.format(IotConst.SEND_POPUP_SN,sn);
        message.setMessage(msg);
        message.setMac(deviceSn);
        message.setProductKey(iotClientUtil.getProductKey());

        AioConfiguration configuration = Aio.getAioConfiguration(iotClientUtil.getProductKey());
        ChannelContext channelContext = configuration.queueList.getChannelContext(deviceSn,configuration);
        channelContext.checkMessageRunnable.addMsg(message);
        channelContext.checkMessageRunnable.execute();
        ReceivePopupSN receivePopupSN = null;
        Boolean res = true;
        int count = 1;
        while (res) {
            if(count>10){
                log.info("查询10次未查询到消息");
                break;
            }
            count++;
            try {
                Thread.sleep(1500);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            receivePopupSN = channelContext.getPopupSnResult(sn);
            if(oConvertUtils.isEmpty(receivePopupSN)){
                configuration.queryMessageRunnable.addMsg(message);
                configuration.queryMessageRunnable.execute();
            }else {
                res = false;
            }
        }
        return receivePopupSN;
    }


    /**
     * sn号弹出
     * @param sn
     * @return
     */
    public ReceivePopupSN sendMsg(String deviceSn){
        CheckMessage message = new CheckMessage();
        message.setMessage(IotConst.SEND_PUISH_VERSION_PUBLISH);
        message.setMac(deviceSn);
        message.setSn(deviceSn);
        message.setProductKey(iotClientUtil.getProductKey());
        AioConfiguration configuration = Aio.getAioConfiguration(iotClientUtil.getProductKey());
        ChannelContext channelContext = configuration.queueList.getChannelContext(deviceSn,configuration);
        channelContext.checkMessageRunnable.addMsg(message);
        channelContext.checkMessageRunnable.execute();

        String topic = "/" + message.getProductKey() + "/" + message.getSn()  + "/user/update";
        byte[] bytes = message.getMessage().getBytes(StandardCharsets.UTF_8);

        try {
            PubResponse response = iotClientUtil.sendMsgAsync(message.getProductKey(), topic, bytes, 1);
        } catch (ClientException e) {
            throw new RuntimeException(e);
        }
        return null;
    }


    /**
     * 发送异步消息
     */
    public PubResponse sendMsgAsync(String productKey, String topicFullName, byte[] bytes, int qos) throws ClientException {
        PubRequest request = new PubRequest();
        String base64 = encodeBase64String(bytes);
        request.setProductKey(productKey);
        request.setMessageContent(base64);
        request.setTopicFullName(topicFullName);
        request.setQos(qos > 1 ? 1 : qos); //目前支持QoS0和QoS1
        PubResponse response = iotClientUtil.getDefaultAcsClient().getAcsResponse(request);
        return response;
    }


    /**
     * 孔位弹出
     * @param hole
     * @return
     */
    public ReceivePopupIndex popupHole(String deviceSn,String hole,String io){

        CheckMessage message = new CheckMessage();
        message.setCmd(IotConst.SEND_CHECK_ALL);
        String msg = String.format(IotConst.SEND_POPUP_HOLE,hole,io);
        message.setMessage(msg);
        message.setSn(deviceSn);
        message.setProductKey(iotClientUtil.getProductKey());

        AioConfiguration configuration = Aio.getAioConfiguration(iotClientUtil.getProductKey());
        ChannelContext channelContext = configuration.queueList.getChannelContext(deviceSn,configuration);
        channelContext.checkMessageRunnable.addMsg(message);
        channelContext.checkMessageRunnable.execute();
        ReceivePopupIndex receivePopupSN = null;
        Boolean res = true;
        int count = 1;
        while (res) {
            if(count>10)break;
            count++;
            try {
                Thread.sleep(1500);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            receivePopupSN = channelContext.getPopupIndexResult(hole);
            if(oConvertUtils.isEmpty(receivePopupSN)){
                configuration.queryMessageRunnable.addMsg(message);
                configuration.queryMessageRunnable.execute();
            }else {
                res = false;
            }
        }
        return receivePopupSN;
    }


    /**
     * 生成设备信息
     * @param categoryId
     * @return
     */
    public YlDevice getQrCodeId(String categoryId) throws IOException {
        if(oConvertUtils.isEmpty(categoryId))throw new ChechDataAuthException("设备类型ID为空");
        QueryWrapper<YlDevice> deviceQueryWrapper = new QueryWrapper<>();
        deviceQueryWrapper
                .eq("device_category",categoryId)
                .eq("device_status",Const.deviceStatus_9).last("limit 1");
        YlDevice temp = deviceService.getOne(deviceQueryWrapper);
        /**
         * 首先查询下是否有未绑定的设备，如果有直接使用
         */
        if(oConvertUtils.isNotEmpty(temp))return temp;
        YlDeviceCategory category = deviceCategoryService.getById(categoryId);
        if(oConvertUtils.isEmpty(category))throw new ChechDataAuthException("未找到对应设备的类型");
        /**
         * 口数，两位格式化
         */
        String prefix = category.getFullNumber()<10?"0":"";
        String id = String.format(IotConst.ID_ATOMIC.incrementAndGet()+""+prefix+category.getFullNumber()+""+category.getRotationNumber());
        /**项目访问地址*/
        String itemUrl = imageUrl+path;
        String content = String.format("%s?id=%s",itemUrl,id);
        BufferedImage aa = QRCodeGenerator.generateQRCodeImage(content, 350, 350);
        String imageUrls = uploadLocal(aa,"temp");
        String realPath = String.format(itemUrl + Const.AVATARURL+imageUrls);
        YlDevice device = new YlDevice();
        device.setDeviceQrcodeId(id);
        device.setDeviceCategory(categoryId);
        device.setDeviceStatus(Const.deviceStatus_9);
        device.setOnlineStatus(2);
        device.setDeviceRemark("初次添加未绑定");
        device.setQrcodeUrl(realPath);
        deviceService.save(device);
        return device;
    }

    /**
     * 删除未绑定的作废的设备
     * @param id
     * @return
     */
    public void deleteDevice(String id){
        YlDevice device = deviceService.getById(id);
        /**
         * 初次添加状态可以删除，绑定后不能删除
         */
        if(Objects.equals(device.getDeviceStatus(),Const.deviceStatus_9))
                deviceService.removeById(id);
    }

    /**
     * 获取全部柜机类型
     * @return
     */
    public List<JSONObject> getCategory(){
        QueryWrapper<YlDeviceCategory> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("device_type",1).ne("parent_id",0);
        List<YlDeviceCategory>categoryList = deviceCategoryService.list(queryWrapper);
        List<JSONObject> list = categoryList.stream().map(e->{
            JSONObject object = new JSONObject();
            object.put("id",e.getId());
            object.put("name",e.getCategoryName());
            object.put("fullNumber",e.getFullNumber());
            object.put("rotationNumber",e.getRotationNumber());
            return object;
        }).collect(Collectors.toList());
        return list;
    }




    /**
     * 本地文件上传
     * @param bufferedImage 文件
     * @param bizPath  自定义路径
     * @return
     */
    public String uploadLocal(BufferedImage bufferedImage, String bizPath) throws IOException {
        InputStream a1 = bufferedImageToInputStream(bufferedImage);
        MultipartFile multipartFile = new MockMultipartFile("qrcode.jpg", "qrcode.jpg", ContentType.APPLICATION_OCTET_STREAM.toString(), a1);
        try {
            String ctxPath = uploadpath;
            String fileName = null;
            File file = new File(ctxPath + File.separator + bizPath + File.separator );
            if (!file.exists()) {
                file.mkdirs();// 创建文件根目录
            }
            String orgName = multipartFile.getOriginalFilename();// 获取文件名
            orgName = CommonUtils.getFileName(orgName);
            if(orgName.indexOf(".")!=-1){
                fileName = orgName.substring(0, orgName.lastIndexOf(".")) + "_" + System.currentTimeMillis() + orgName.substring(orgName.indexOf("."));
            }else{
                fileName = orgName+ "_" + System.currentTimeMillis();
            }
            String savePath = file.getPath() + File.separator + fileName;
            File savefile = new File(savePath);
            FileCopyUtils.copy(multipartFile.getBytes(), savefile);
            String dbpath = null;
            if(oConvertUtils.isNotEmpty(bizPath)){
                dbpath = bizPath + File.separator + fileName;
            }else{
                dbpath = fileName;
            }
            if (dbpath.contains("\\")) {
                dbpath = dbpath.replace("\\", "/");
            }
            return dbpath;
        } catch (IOException e) {
            log.error(e.getMessage(), e);
        }
        return "";
    }

    /**
     * 将BufferedImage转换为InputStream
     * @param image
     * @return
     */
    public InputStream bufferedImageToInputStream(BufferedImage image){
        ByteArrayOutputStream os = new ByteArrayOutputStream();
        try {
            ImageIO.write(image, "png", os);
            InputStream input = new ByteArrayInputStream(os.toByteArray());
            return input;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }


    public JSONObject jsonEntity(ReceiveUpload receiveUpload){

        JSONObject entity = new JSONObject();

        JSONArray jsonArray = new JSONArray(); //串口

        List<Pinboard> pinboards = receiveUpload.getPinboards();

        for(Pinboard pinboard:pinboards){
            JSONObject pb = new JSONObject();

            pb.put("index",pinboard.getIndex());
            pb.put("undefined1",pinboard.getUndefined1());
            pb.put("undefined2",pinboard.getUndefined2());
            pb.put("temp",pinboard.getTemp());
            pb.put("softVersion",pinboard.getSoftVersion());
            pb.put("hardVersion",pinboard.getHardVersion());

            JSONArray powerBank = new JSONArray(); //串口

            List<Powerbank> powerbanks = pinboard.getPowerbanks();
            for(Powerbank powerbank:powerbanks){
                JSONObject powers = new JSONObject();
                powers.put("pinboardIndex",powerbank.getPinboardIndex());
                powers.put("index",powerbank.getIndex());
                powers.put("status",powerbank.getStatus());
                powers.put("undefined1",powerbank.getUndefined1());
                powers.put("undefined2",powerbank.getUndefined2());
                powers.put("area",powerbank.getArea());
                powers.put("power",powerbank.getPower());
                powers.put("temp",powerbank.getTemp());
                powers.put("voltage",powerbank.getVoltage());
                powers.put("current",powerbank.getCurrent());
                powers.put("softVersion",powerbank.getSoftVersion());
                powers.put("hardVersion",powerbank.getHardVersion());
                powers.put("snAsString",powerbank.getSnAsString());
                powerBank.add(powers);
            }
            pb.put("powerbanks",powerBank);
            jsonArray.add(pb);
        }
        entity.put("pinboards",jsonArray);
        return entity;
    }

    public JSONObject jsonEntity(ReceiveUpload receiveUpload,String deviceSn){

        JSONObject entity = new JSONObject();
        entity.put("deviceSn",deviceSn);
        JSONArray jsonArray = new JSONArray(); //串口

        List<Pinboard> pinboards = receiveUpload.getPinboards();

        for(Pinboard pinboard:pinboards){
            JSONObject pb = new JSONObject();

            pb.put("index",pinboard.getIndex());
            pb.put("undefined1",pinboard.getUndefined1());
            pb.put("undefined2",pinboard.getUndefined2());
            pb.put("temp",pinboard.getTemp());
            pb.put("softVersion",pinboard.getSoftVersion());
            pb.put("hardVersion",pinboard.getHardVersion());

            JSONArray powerBank = new JSONArray(); //串口

            List<Powerbank> powerbanks = pinboard.getPowerbanks();
            for(Powerbank powerbank:powerbanks){
                JSONObject powers = new JSONObject();
                powers.put("pinboardIndex",powerbank.getPinboardIndex());
                powers.put("index",powerbank.getIndex());
                powers.put("status",powerbank.getStatus());
                powers.put("undefined1",powerbank.getUndefined1());
                powers.put("undefined2",powerbank.getUndefined2());
                powers.put("area",powerbank.getArea());
                powers.put("power",powerbank.getPower());
                powers.put("temp",powerbank.getTemp());
                powers.put("voltage",powerbank.getVoltage());
                powers.put("current",powerbank.getCurrent());
                powers.put("softVersion",powerbank.getSoftVersion());
                powers.put("hardVersion",powerbank.getHardVersion());
                powers.put("snAsString",powerbank.getSnAsString());
                powerBank.add(powers);
            }
            pb.put("powerbanks",powerBank);
            jsonArray.add(pb);
        }
        entity.put("pinboards",jsonArray);
        return entity;
    }
}
